import os
import json
import re
import csv
import tkinter as tk
from tkinter import filedialog, scrolledtext, messagebox, ttk, simpledialog
import threading
import struct
from collections import defaultdict
from io import BytesIO
import configparser  # 用于保存配置
import sys

def get_base_dir():
    if getattr(sys, 'frozen', False):
        # 打包环境
        return os.path.dirname(sys.executable)
    else:
        # 脚本环境
        return os.path.dirname(os.path.abspath(__file__))

def find_files(directory):
    """
    递归遍历指定目录及其子目录中的所有文件，并返回所有文件的路径。
    忽略以点(.)开头的文件或目录。
    """
    for root, dirs, files in os.walk(directory):
        files = [f for f in files if not f[0] == '.']
        dirs[:] = [d for d in dirs if not d[0] == '.']
        for file in files:
            yield os.path.join(root, file)


def contains_chinese_characters(text):
    """
    检查一个字符串是否包含中文字符。
    """
    return bool(re.search('[\u4e00-\u9fff]', text))


def recursive_iter(obj):
    """
    递归遍历一个对象中所有的键值对。
    """
    if isinstance(obj, dict):
        for _, value in obj.items():
            yield from recursive_iter(value)
    elif any(isinstance(obj, t) for t in (list, tuple)):
        for value in obj:
            yield from recursive_iter(value)
    else:
        yield obj


class ChineseExtractorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("中文字符提取器")
        self.root.geometry("800x600")

        # 存储提取到的中文字符及其来源
        self.chinese_texts = set()
        self.text_sources = {}  # 存储每个中文字符的来源文件

        # 处理状态
        self.processing = False

        # 配置文件路径
        self.config_dir = os.path.join(get_base_dir(), "chinese_extractor_configs")
        # 确保配置目录存在
        if not os.path.exists(self.config_dir):
            os.makedirs(self.config_dir)

        # 检查是否存在旧的配置文件，如果存在则迁移
        old_config_file = os.path.join(get_base_dir(), "chinese_extractor_config.ini")
        if os.path.exists(old_config_file):
            # 将旧配置文件复制到新目录
            import shutil
            new_file = os.path.join(self.config_dir, "default.ini")
            if not os.path.exists(new_file):
                shutil.copy2(old_config_file, new_file)

        # 默认配置文件名
        self.config_name = "default"
        self.config_file = os.path.join(self.config_dir, f"{self.config_name}.ini")

        # 路径和启用状态
        self.path_enabled = {}  # 存储路径的启用状态

        # 加载配置
        self.load_config()

        # 创建界面元素
        self.create_widgets()

    def load_config(self, config_name=None):
        """加载配置文件

        Args:
            config_name: 配置文件名，不包含扩展名。如果为None，则使用当前配置名
        """
        if config_name:
            self.config_name = config_name
            self.config_file = os.path.join(self.config_dir, f"{self.config_name}.ini")

        config = configparser.ConfigParser()

        # 默认路径列表
        default_paths = [
            os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "page"),
            "C:\\Users\\jan\\Documents\\Project\\CproScada\\server",
            "C:\\Users\\jan\\Documents\\Project\\CproScada\\cpro_scada",
            "C:\\Users\\jan\\AppData\\Roaming\\Mindteco\\app_userdata\\Cpro\\auth_conf",
            "C:\\Users\\jan\\AppData\\Roaming\\Mindteco\\app_userdata\\Cpro\\cpro_config\\cpro_recipe"
        ]

        # 清空当前路径
        self.path_enabled.clear()

        if os.path.exists(self.config_file):
            try:
                config.read(self.config_file)

                # 读取路径和启用状态
                if 'Paths' in config:
                    for key in config['Paths']:
                        path_value = config['Paths'][key]
                        enabled = True
                        if key.endswith('_enabled'):
                            continue

                        # 检查是否有对应的启用状态
                        enabled_key = f"{key}_enabled"
                        if enabled_key in config['Paths']:
                            enabled = config['Paths'].getboolean(enabled_key)

                        self.path_enabled[path_value] = enabled

                # 更新标题显示当前配置名
                self.root.title(f"中文字符提取器 - {self.config_name}")

                # 如果有配置名标签，更新它
                if hasattr(self, 'config_name_label'):
                    self.config_name_label.config(text=f"当前配置: {self.config_name}")

            except Exception as e:
                messagebox.showerror("配置加载错误", f"加载配置文件时出错：{str(e)}")
                # 使用默认路径
                for path in default_paths:
                    self.path_enabled[path] = True
        else:
            # 使用默认路径
            for path in default_paths:
                self.path_enabled[path] = True

    def save_config(self, config_name=None, show_message=True):
        """保存配置到文件

        Args:
            config_name: 配置文件名，不包含扩展名。如果为None，则使用当前配置名
            show_message: 是否显示保存成功的提示，默认为True
        """
        if config_name:
            self.config_name = config_name
            self.config_file = os.path.join(self.config_dir, f"{self.config_name}.ini")

        config = configparser.ConfigParser()
        config['Paths'] = {}

        # 保存路径和启用状态
        for i, (path, enabled) in enumerate(self.path_enabled.items()):
            config['Paths'][f"path{i}"] = path
            config['Paths'][f"path{i}_enabled"] = str(enabled)

        try:
            with open(self.config_file, 'w') as f:
                config.write(f)

            # 更新标题显示当前配置名
            self.root.title(f"中文字符提取器 - {self.config_name}")

            # 如果有配置名标签，更新它
            if hasattr(self, 'config_name_label'):
                self.config_name_label.config(text=f"当前配置: {self.config_name}")

            if show_message:
                messagebox.showinfo("保存成功", f"配置已保存为 {self.config_name}")
        except Exception as e:
            messagebox.showerror("配置保存错误", f"保存配置文件时出错：{str(e)}")

    def save_config_as(self):
        """另存为新配置"""
        # 弹出对话框，让用户输入新的配置名
        new_config_name = simpledialog.askstring("另存为", "请输入配置名称:", initialvalue=self.config_name)
        if new_config_name:
            # 保存配置
            self.save_config(new_config_name)

    def load_config_dialog(self):
        """加载配置对话框"""
        # 获取所有配置文件
        config_files = []
        for file in os.listdir(self.config_dir):
            if file.endswith('.ini'):
                config_files.append(os.path.splitext(file)[0])

        if not config_files:
            messagebox.showinfo("提示", "没有找到配置文件")
            return

        # 创建对话框
        dialog = tk.Toplevel(self.root)
        dialog.title("加载配置")
        dialog.geometry("300x400")
        dialog.transient(self.root)
        dialog.grab_set()

        # 创建列表框
        listbox = tk.Listbox(dialog, width=40, height=15)
        listbox.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)

        # 添加配置文件到列表
        for config_name in sorted(config_files):
            listbox.insert(tk.END, config_name)

        # 选择当前配置
        try:
            current_index = config_files.index(self.config_name)
            listbox.selection_set(current_index)
            listbox.see(current_index)
        except ValueError:
            pass

        # 创建按钮框架
        btn_frame = tk.Frame(dialog)
        btn_frame.pack(pady=10, fill=tk.X)

        # 加载按钮
        def on_load():
            selection = listbox.curselection()
            if selection:
                selected_config = listbox.get(selection[0])
                dialog.destroy()
                # 重新加载UI
                self.reload_ui_with_config(selected_config)
            else:
                messagebox.showinfo("提示", "请选择一个配置")

        # 删除按钮
        def on_delete():
            selection = listbox.curselection()
            if selection:
                selected_config = listbox.get(selection[0])
                if selected_config == self.config_name:
                    messagebox.showinfo("提示", "不能删除当前正在使用的配置")
                    return

                if messagebox.askyesno("确认", f"确定要删除配置 {selected_config} 吗?"):
                    config_path = os.path.join(self.config_dir, f"{selected_config}.ini")
                    try:
                        os.remove(config_path)
                        listbox.delete(selection[0])
                        messagebox.showinfo("成功", f"配置 {selected_config} 已删除")
                    except Exception as e:
                        messagebox.showerror("错误", f"删除配置失败: {str(e)}")
            else:
                messagebox.showinfo("提示", "请选择一个配置")

        load_btn = tk.Button(btn_frame, text="加载", command=on_load)
        load_btn.pack(side=tk.LEFT, padx=10)

        delete_btn = tk.Button(btn_frame, text="删除", command=on_delete)
        delete_btn.pack(side=tk.LEFT, padx=10)

        cancel_btn = tk.Button(btn_frame, text="取消", command=dialog.destroy)
        cancel_btn.pack(side=tk.RIGHT, padx=10)

    def reload_ui_with_config(self, config_name):
        """重新加载UI，使用指定的配置"""
        # 加载配置
        self.load_config(config_name)

        # 清空路径框架
        for widget in self.paths_scrollable_frame.winfo_children():
            widget.destroy()

        # 清空路径变量和输入框
        self.path_vars.clear()
        self.path_entries.clear()

        # 添加路径按钮
        add_path_frame = ttk.Frame(self.paths_scrollable_frame)
        add_path_frame.pack(fill=tk.X, pady=2)

        add_path_btn = ttk.Button(add_path_frame, text="添加路径", command=self.add_new_path)
        add_path_btn.pack(side=tk.RIGHT)

        # 为每个路径创建一行
        for path, enabled in self.path_enabled.items():
            self.create_path_row(self.paths_scrollable_frame, path, enabled)

    def create_widgets(self):
        # 创建顶部按钮框架
        button_frame = tk.Frame(self.root)
        button_frame.pack(pady=10, fill=tk.X)

        # 配置名标签
        self.config_name_label = tk.Label(button_frame, text=f"当前配置: {self.config_name}")
        self.config_name_label.pack(side=tk.LEFT, padx=10)

        # 执行按钮
        self.execute_btn = tk.Button(button_frame, text="执行", command=self.execute_extraction)
        self.execute_btn.pack(side=tk.LEFT, padx=10)

        # 查看来源按钮
        self.source_btn = tk.Button(button_frame, text="查看来源文件", command=self.show_all_sources)
        self.source_btn.pack(side=tk.LEFT, padx=10)

        # 配置管理按钮
        config_frame = tk.Frame(button_frame)
        config_frame.pack(side=tk.RIGHT, padx=10)

        save_btn = tk.Button(config_frame, text="保存配置", command=lambda: self.save_config())
        save_btn.pack(side=tk.LEFT, padx=5)

        save_as_btn = tk.Button(config_frame, text="另存为", command=self.save_config_as)
        save_as_btn.pack(side=tk.LEFT, padx=5)

        load_btn = tk.Button(config_frame, text="加载配置", command=self.load_config_dialog)
        load_btn.pack(side=tk.LEFT, padx=5)

        # 创建状态栏，显示项目数量
        self.status_frame = tk.Frame(self.root)
        self.status_frame.pack(fill=tk.X, pady=5)

        self.count_label = tk.Label(self.status_frame, text="当前项目数: 0")
        self.count_label.pack(side=tk.LEFT, padx=10)

        # 创建参考路径框架，给予更多空间
        paths_frame = tk.LabelFrame(self.root, text="参考路径（勾选启用）", padx=1, pady=1)
        paths_frame.pack(fill=tk.BOTH, padx=10, pady=5, expand=True)

        # 创建路径容器，带滚动条，确保获得足够空间
        paths_canvas = tk.Canvas(paths_frame)
        paths_scrollbar = ttk.Scrollbar(paths_frame, orient="vertical", command=paths_canvas.yview)
        self.paths_scrollable_frame = ttk.Frame(paths_canvas)

        # 定义一个函数来处理Configure事件
        def on_frame_configure(_):
            paths_canvas.configure(scrollregion=paths_canvas.bbox("all"))

        self.paths_scrollable_frame.bind("<Configure>", on_frame_configure)

        paths_canvas.create_window((0, 0), window=self.paths_scrollable_frame, anchor="nw")
        paths_canvas.configure(yscrollcommand=paths_scrollbar.set)

        # 使用pack_propagate(False)确保Canvas不会根据内容调整大小
        paths_canvas.pack_propagate(False)

        # 设置最小高度
        paths_frame.update()
        min_height = 200  # 设置最小高度为200像素
        paths_canvas.config(height=min_height)

        paths_canvas.pack(side="left", fill="both", expand=True)
        paths_scrollbar.pack(side="right", fill="y")

        # 路径变量和复选框
        self.path_vars = {}  # 存储路径的Checkbutton变量
        self.path_entries = {}  # 存储路径的Entry控件

        # 添加路径按钮
        add_path_frame = ttk.Frame(self.paths_scrollable_frame)
        add_path_frame.pack(fill=tk.X, pady=2)

        add_path_btn = ttk.Button(add_path_frame, text="添加路径", command=self.add_new_path)
        add_path_btn.pack(side=tk.RIGHT)

        # 为每个路径创建一行
        for path, enabled in self.path_enabled.items():
            self.create_path_row(self.paths_scrollable_frame, path, enabled)

        # 创建文本编辑区域，减少默认高度以给路径部分更多空间
        self.text_area = scrolledtext.ScrolledText(self.root, width=100, height=20)
        self.text_area.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)

    def create_path_row(self, parent, path, enabled=True):
        """创建一个路径行，包含复选框、路径显示和删除按钮"""
        # 主框架
        path_frame = ttk.Frame(parent)
        path_frame.pack(fill=tk.X, pady=2, expand=True)

        # 创建复选框变量
        var = tk.BooleanVar(value=enabled)
        self.path_vars[path] = var

        # 复选框
        check = ttk.Checkbutton(path_frame, variable=var, command=self.update_path_status)
        check.pack(side=tk.LEFT)

        # 删除按钮
        delete_btn = ttk.Button(path_frame, text="删除",
                               command=lambda p=path, f=path_frame: self.delete_path(p, f))
        delete_btn.pack(side=tk.RIGHT, padx=2)

        # 创建文本框容器，占据大部分空间
        text_container = ttk.Frame(path_frame)
        text_container.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)

        # 创建水平滚动的文本框，设置为只读
        path_text = tk.Text(text_container, height=1, wrap="none")
        path_text.insert("1.0", path)
        path_text.config(state="disabled")  # 设置为只读

        # 添加水平滚动条
        path_scrollbar = ttk.Scrollbar(text_container, orient="horizontal", command=path_text.xview)
        path_text.config(xscrollcommand=path_scrollbar.set)

        # 放置文本框和滚动条，确保文本框撑满可用空间
        path_text.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        path_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)

        self.path_entries[path] = path_text

        return path_frame

    def add_new_path(self):
        """添加新路径"""
        directory = filedialog.askdirectory()
        if directory:
            # 检查是否已存在
            if directory in self.path_enabled:
                messagebox.showinfo("提示", "该路径已存在")
                return

            # 添加到路径字典
            self.path_enabled[directory] = True

            # 创建路径行
            self.create_path_row(self.paths_scrollable_frame, directory)

    def browse_path(self, old_path):
        """浏览并更新路径"""
        directory = filedialog.askdirectory()
        if directory:
            # 更新路径字典
            enabled = self.path_enabled.pop(old_path, True)
            self.path_enabled[directory] = enabled

            # 更新路径变量
            var = self.path_vars.pop(old_path, None)
            if var:
                self.path_vars[directory] = var

            # 更新路径文本框
            text_widget = self.path_entries.pop(old_path, None)
            if text_widget:
                text_widget.config(state="normal")  # 临时启用编辑
                text_widget.delete("1.0", tk.END)
                text_widget.insert("1.0", directory)
                text_widget.config(state="disabled")  # 恢复只读状态
                self.path_entries[directory] = text_widget

    def delete_path(self, path, frame):
        """删除路径"""
        # 从字典中删除
        if path in self.path_enabled:
            del self.path_enabled[path]

        # 从变量字典中删除
        if path in self.path_vars:
            del self.path_vars[path]

        # 从输入框字典中删除
        if path in self.path_entries:
            del self.path_entries[path]

        # 销毁框架
        frame.destroy()

    def update_path_status(self):
        """更新路径启用状态"""
        # 更新路径字典中的启用状态
        for path, var in self.path_vars.items():
            self.path_enabled[path] = var.get()


    def execute_extraction(self):
        """执行提取操作"""
        if self.processing:
            messagebox.showwarning("警告", "正在处理文件，请等待完成")
            return

        # 获取启用的路径
        enabled_paths = []
        for path, enabled in self.path_enabled.items():
            if enabled:
                # 检查路径是否存在
                if os.path.exists(path):
                    enabled_paths.append(path)
                else:
                    messagebox.showwarning("警告", f"路径不存在: {path}")

        if not enabled_paths:
            messagebox.showwarning("警告", "没有启用的有效路径")
            return

        # 在新线程中处理目录
        self.processing = True
        self.execute_btn.config(state=tk.DISABLED)
        threading.Thread(target=self.process_directories, args=(enabled_paths,), daemon=True).start()

    def process_directories(self, directories):
        """处理多个目录"""
        # 清空文本区域和中文字符集合
        self.text_area.delete(1.0, tk.END)
        self.chinese_texts.clear()
        self.text_sources.clear()

        try:
            # 处理每个目录
            for directory in directories:
                self.extract_chinese_from_directory(directory)

            # 在主线程中更新UI
            self.root.after(0, self.update_ui_with_results)
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("错误", f"处理过程中发生错误：\n{str(e)}"))
        finally:
            # 恢复UI状态
            self.root.after(0, lambda: self.execute_btn.config(state=tk.NORMAL))
            self.processing = False

    def update_ui_with_results(self):
        """更新UI显示结果"""
        # 清空文本区域
        self.text_area.delete(1.0, tk.END)

        # 在文本区域显示结果，一行一个
        for text in sorted(self.chinese_texts):
            self.text_area.insert(tk.END, f"{text}\n")

        # 更新项目数量显示
        self.count_label.config(text=f"当前项目数: {len(self.chinese_texts)}")

        # 显示统计信息
        messagebox.showinfo("提取完成", f"共提取到 {len(self.chinese_texts)} 个中文字符串")

    def show_all_sources(self):
        """显示所有来源文件"""
        if not self.text_sources:
            messagebox.showinfo("提示", "没有来源信息，请先选择目录提取中文字符")
            return

        # 收集所有唯一的来源文件
        all_sources = set()
        for sources in self.text_sources.values():
            all_sources.update(sources)

        # 创建一个自定义对话框，带滚动条
        source_dialog = tk.Toplevel(self.root)
        source_dialog.title("来源文件")
        source_dialog.geometry("600x400")

        # 添加标题标签
        title_label = tk.Label(source_dialog, text="所有中文字符来源于以下文件：", font=("Arial", 12))
        title_label.pack(pady=10)

        # 添加带滚动条的文本区域
        source_text_area = scrolledtext.ScrolledText(source_dialog, width=70, height=20)
        source_text_area.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)

        # 填充文本区域
        for source in sorted(all_sources):
            source_text_area.insert(tk.END, f"- {source}\n")

        # 设置为只读
        source_text_area.config(state=tk.DISABLED)

        # 添加关闭按钮
        close_button = tk.Button(source_dialog, text="关闭", command=source_dialog.destroy)
        close_button.pack(pady=10)

    def decode_unsigned_varint(self, data, offset=0):
        """解码无符号varint"""
        result = 0
        shift = 0
        pos = offset

        while True:
            if pos >= len(data):
                return None, pos

            b = data[pos]
            pos += 1

            result |= ((b & 0x7f) << shift)
            shift += 7

            if not (b & 0x80):
                break

        return result, pos

    def parse_mmkv_file(self, file_path):
        """解析MMKV文件，返回键值对字典"""
        try:
            with open(file_path, 'rb') as f:
                data = f.read()

            # 读取数据库大小（前4个字节）
            if len(data) < 4:
                print(f"MMKV文件 {file_path} 太小，无法解析")
                return {}

            db_size = struct.unpack('<I', data[:4])[0]
            print(f"MMKV文件 {file_path} 的db_size={db_size}")

            # 如果数据库大小为0，使用最大值
            if db_size == 0:
                db_size = 2**32

            # 创建结果字典
            result = defaultdict(list)

            # 从第4个字节开始解析
            pos = 4

            # 直接尝试提取所有可能的JSON对象
            json_pattern = re.compile(rb'\{[^\{]*"@N"[^\}]*\}')
            for match in json_pattern.finditer(data):
                json_str = match.group(0).decode('utf-8', errors='ignore')

                # 检查JSON字符串是否完整，如果不完整则尝试修复
                if not json_str.endswith('}'):
                    json_str += '}'

                try:
                    # 尝试解析JSON，只是为了验证是否为有效的JSON
                    json_obj = json.loads(json_str)

                    # 特别检查@OKI.@N字段
                    if isinstance(json_obj, dict) and '@OKI' in json_obj and isinstance(json_obj['@OKI'], dict) and '@N' in json_obj['@OKI']:
                        n_value = json_obj['@OKI']['@N']
                        if isinstance(n_value, str) and contains_chinese_characters(n_value):
                            # 直接添加@N值到结果中
                            result["n_value_" + str(match.start())].append(n_value.encode('utf-8'))

                    # 不再添加整个JSON字符串到结果中

                    # 打印找到的JSON对象，用于调试
                    print(f"找到JSON对象: {json_str[:100]}...")
                except json.JSONDecodeError as e:
                    print(f"JSON解析错误: {e}, JSON字符串: {json_str[:100]}...")

            # 尝试提取所有可能的中文字符串
            # 注意：二进制数据中的中文字符通常是UTF-8编码的，需要先找到可能的UTF-8序列
            # 查找所有可能包含中文字符的UTF-8序列
            for i in range(0, len(data) - 2):
                # 检查是否是UTF-8编码的中文字符的开始
                if 0xE0 <= data[i] <= 0xEF and i + 2 < len(data):
                    # 尝试解码3字节的UTF-8序列
                    try:
                        chunk = data[i:i+3]
                        text = chunk.decode('utf-8')
                        # 检查是否是中文字符
                        if contains_chinese_characters(text):
                            # 尝试扩展找到完整的中文字符串
                            start = i
                            end = i + 3
                            # 向前扩展
                            while start > 0 and 0xE0 <= data[start-3] <= 0xEF:
                                try:
                                    test_chunk = data[start-3:start]
                                    test_text = test_chunk.decode('utf-8')
                                    if contains_chinese_characters(test_text):
                                        start -= 3
                                    else:
                                        break
                                except UnicodeDecodeError:
                                    break
                            # 向后扩展
                            while end + 3 <= len(data) and 0xE0 <= data[end] <= 0xEF:
                                try:
                                    test_chunk = data[end:end+3]
                                    test_text = test_chunk.decode('utf-8')
                                    if contains_chinese_characters(test_text):
                                        end += 3
                                    else:
                                        break
                                except UnicodeDecodeError:
                                    break

                            # 提取完整的中文字符串
                            try:
                                full_text = data[start:end].decode('utf-8')
                                if len(full_text) > 1:  # 忽略单个中文字符
                                    result["chinese_" + str(start)].append(full_text.encode('utf-8'))
                                    print(f"找到中文字符串: {full_text}")
                                    # 跳过已处理的字节
                                    i = end - 1
                            except UnicodeDecodeError:
                                pass
                    except UnicodeDecodeError:
                        pass

            # 如果上面的方法没有找到任何内容，尝试使用原始的解析方法
            if not result:
                print(f"尝试使用原始解析方法解析MMKV文件 {file_path}")
                # 解析键值对
                while pos < min(len(data), db_size + 4):
                    # 解析键长度
                    key_length, pos = self.decode_unsigned_varint(data, pos)
                    if key_length is None or key_length == 0:
                        break

                    # 读取键（UTF-8字符串）
                    if pos + key_length > len(data):
                        break

                    key_bytes = data[pos:pos+key_length]
                    pos += key_length

                    try:
                        key = key_bytes.decode('utf-8')
                    except UnicodeDecodeError:
                        continue

                    # 解析值长度
                    value_length, pos = self.decode_unsigned_varint(data, pos)
                    if value_length is None or value_length == 0:
                        continue

                    # 读取值（字节）
                    if pos + value_length > len(data):
                        break

                    value_bytes = data[pos:pos+value_length]
                    pos += value_length

                    # 添加到结果字典
                    result[key].insert(0, value_bytes)

            print(f"从MMKV文件 {file_path} 中提取了 {len(result)} 个键值对")
            return result
        except Exception as e:
            print(f"解析MMKV文件 {file_path} 时出错: {e}")
            return {}

    def add_chinese_text(self, text, file_path):
        """添加中文文本到结果集合中"""
        if not text or not contains_chinese_characters(text):
            return

        # 获取相对路径
        relative_path = file_path
        try:
            cwd = os.getcwd()
            if file_path.startswith(cwd):
                relative_path = os.path.relpath(file_path, cwd)
        except Exception:
            pass

        # 添加到结果集合
        if len(text) > 1:  # 忽略单个中文字符
            self.chinese_texts.add(text)
            # 记录来源
            if text not in self.text_sources:
                self.text_sources[text] = []
            if relative_path not in self.text_sources[text]:
                self.text_sources[text].append(relative_path)

    def extract_chinese_from_json(self, json_obj, file_path):
        """从JSON对象中提取中文字符"""
        if isinstance(json_obj, dict):
            # 遍历字典的所有键值对
            for key, value in json_obj.items():
                # 检查值是否为字符串并包含中文
                if isinstance(value, str) and contains_chinese_characters(value):
                    self.add_chinese_text(value, file_path)

                # 检查嵌套对象中的 @N 字段（显示名称）
                if key == "@OKI" and isinstance(value, dict) and "@N" in value:
                    n_value = value["@N"]
                    if isinstance(n_value, str) and contains_chinese_characters(n_value):
                        # 对于 @N 字段，即使是单个中文字符也提取
                        self.chinese_texts.add(n_value)

                        # 记录来源
                        relative_path = file_path
                        try:
                            cwd = os.getcwd()
                            if file_path.startswith(cwd):
                                relative_path = os.path.relpath(file_path, cwd)
                        except Exception:
                            pass

                        if n_value not in self.text_sources:
                            self.text_sources[n_value] = []
                        if relative_path not in self.text_sources[n_value]:
                            self.text_sources[n_value].append(relative_path)

                # 递归处理嵌套对象
                if isinstance(value, (dict, list)):
                    self.extract_chinese_from_json(value, file_path)

        elif isinstance(json_obj, list):
            # 遍历列表中的所有元素
            for item in json_obj:
                self.extract_chinese_from_json(item, file_path)

    def extract_chinese_from_directory(self, directory):
        """提取目录中所有文件的中文字符"""
        for file_path in find_files(directory):
            self.extract_chinese_from_file(file_path)

    def extract_chinese_from_file(self, file_path):
        """从单个文件中提取中文字符"""
        # 跳过大文件
        try:
            file_size = os.path.getsize(file_path)
            if file_size > 10 * 1024 * 1024:  # 跳过大于10MB的文件
                print(f"跳过大文件: {file_path} ({file_size / 1024 / 1024:.2f} MB)")
                return
        except Exception:
            pass

        # 记录文件的相对路径，使其更易读
        relative_path = file_path
        try:
            # 尝试获取相对于当前目录的路径
            cwd = os.getcwd()
            if file_path.startswith(cwd):
                relative_path = os.path.relpath(file_path, cwd)
        except Exception:
            pass

        if file_path.endswith('.vue'):
            # 处理Vue文件
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    text = file.read()

                    # 1. 查找 $t(...) 中的中文字符串，支持单引号和双引号
                    vue_pattern = re.compile(r'\$t\([\'"]([\u4e00-\u9fa5]+(?:[^\'"]*[\u4e00-\u9fa5]+)*)[\'"]\)')
                    for match in vue_pattern.finditer(text):
                        chinese_text = match.group(1)
                        if chinese_text:
                            self.chinese_texts.add(chinese_text)
                            # 记录来源
                            if chinese_text not in self.text_sources:
                                self.text_sources[chinese_text] = []
                            if relative_path not in self.text_sources[chinese_text]:
                                self.text_sources[chinese_text].append(relative_path)

                    # 2. 查找普通的中文字符串
                    chinese_pattern = re.compile('[\u4e00-\u9fff]+')
                    for match in chinese_pattern.finditer(text):
                        chinese_text = match.group()
                        if len(chinese_text) > 1:  # 忽略单个中文字符
                            self.chinese_texts.add(chinese_text)
                            # 记录来源
                            if chinese_text not in self.text_sources:
                                self.text_sources[chinese_text] = []
                            if relative_path not in self.text_sources[chinese_text]:
                                self.text_sources[chinese_text].append(relative_path)
            except Exception as e:
                print(f"处理Vue文件 {file_path} 时出错: {e}")

        elif file_path.endswith('.gd'):
            # 跳过V.gd文件
            if os.path.basename(file_path).startswith('V'):
                print(f"跳过V.gd文件: {file_path}")
                return

            # 处理Godot脚本文件
            for encoding in ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1']:
                try:
                    with open(file_path, 'r', encoding=encoding) as file:
                        text = file.read()

                        # 移除单行注释
                        text = re.sub(r'#.*$', '', text, flags=re.MULTILINE)
                        
                        # 移除多行注释
                        text = re.sub(r'""".*?"""', '', text, flags=re.DOTALL)
                        text = re.sub(r"'''.*?'''", '', text, flags=re.DOTALL)

                        # 移除 printerr 函数的内容
                        text = re.sub(r'printerr\([^)]*\)', '', text)

                        # 1. 查找所有被引号包裹的字符串
                        string_pattern = re.compile(r'["\'](.*?)["\']')
                        for match in string_pattern.finditer(text):
                            string_value = match.group(1)
                            
                            # 检查是否包含花括号
                            if '{' in string_value and '}' in string_value:
                                # 如果包含花括号，只提取花括号中的内容
                                bracket_pattern = re.compile(r'\{([^}]*?[\u4e00-\u9fff][^}]*?)\}')
                                for bracket_match in bracket_pattern.finditer(string_value):
                                    bracket_content = bracket_match.group(1)
                                    if contains_chinese_characters(bracket_content):
                                        self.chinese_texts.add(bracket_content)
                                        # 记录来源
                                        if bracket_content not in self.text_sources:
                                            self.text_sources[bracket_content] = []
                                        if relative_path not in self.text_sources[bracket_content]:
                                            self.text_sources[bracket_content].append(relative_path)
                            else:
                                # 如果不包含花括号，处理整个字符串
                                if contains_chinese_characters(string_value):
                                    self.chinese_texts.add(string_value)
                                    # 记录来源
                                    if string_value not in self.text_sources:
                                        self.text_sources[string_value] = []
                                    if relative_path not in self.text_sources[string_value]:
                                        self.text_sources[string_value].append(relative_path)
                    break  # 如果成功打开文件，跳出循环
                except UnicodeDecodeError:
                    continue  # 如果编码错误，尝试下一种编码
                except Exception as e:
                    print(f"处理Godot脚本文件 {file_path} 时出错: {e}")
                    break

        elif file_path.endswith('.tscn'):
            # 处理Godot场景文件
            for encoding in ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1']:
                try:
                    with open(file_path, 'r', encoding=encoding) as file:
                        text = file.read()

                        # 按节点分割文件内容
                        node_pattern = re.compile(r'\[node.*?\](.*?)(?=\[node|\Z)', re.DOTALL)
                        for node_match in node_pattern.finditer(text):
                            node_content = node_match.group(1)
                            
                            # 在节点内查找 text 和 window_title 属性
                            text_pattern = re.compile(r'(?:text|window_title)\s*=\s*"([^"]*)"')
                            for text_match in text_pattern.finditer(node_content):
                                text_value = text_match.group(1)
                                if contains_chinese_characters(text_value):
                                    self.add_chinese_text(text_value, file_path)
                    break  # 如果成功打开文件，跳出循环
                except UnicodeDecodeError:
                    continue  # 如果编码错误，尝试下一种编码
                except Exception as e:
                    print(f"处理Godot场景文件 {file_path} 时出错: {e}")
                    break

        elif file_path.endswith('.csv'):
            # 尝试不同的编码打开CSV文件
            for encoding in ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1']:
                try:
                    with open(file_path, 'r', encoding=encoding) as file:
                        csv_reader = csv.reader(file)
                        for row in csv_reader:
                            for cell in row:
                                if contains_chinese_characters(cell):
                                    self.chinese_texts.add(cell)
                                    # 记录来源
                                    if cell not in self.text_sources:
                                        self.text_sources[cell] = []
                                    if relative_path not in self.text_sources[cell]:
                                        self.text_sources[cell].append(relative_path)
                    break  # 如果成功打开文件，跳出循环
                except UnicodeDecodeError:
                    continue  # 如果编码错误，尝试下一种编码
                except Exception as e:
                    print(f"处理CSV文件 {file_path} 时出错: {e}")
                    break

        elif file_path.endswith('.crc'):
            # 处理MMKV文件 - 如果找到 auth.crc，查找对应的 auth 文件
            if os.path.basename(file_path) == 'auth.crc':
                # 获取auth文件路径
                auth_file_path = os.path.join(os.path.dirname(file_path), 'auth')
                if os.path.exists(auth_file_path):
                    try:
                        # 使用改进的MMKV解析方法
                        mmkv_data = self.parse_mmkv_file(auth_file_path)

                        # 处理解析出的数据 - 只关注中文值
                        chinese_values = set()  # 使用集合避免重复

                        # 从mmkv_data中提取中文值
                        for key, values in mmkv_data.items():
                            # 如果键以"n_value_"开头，这是我们特别提取的@N值
                            if key.startswith("n_value_"):
                                for value in values:
                                    try:
                                        string_value = value.decode('utf-8', errors='ignore')
                                        if contains_chinese_characters(string_value):
                                            chinese_values.add(string_value)
                                    except Exception:
                                        pass
                            # 如果键以"chinese_"开头，这是我们直接提取的中文字符串
                            elif key.startswith("chinese_"):
                                for value in values:
                                    try:
                                        string_value = value.decode('utf-8', errors='ignore')
                                        if contains_chinese_characters(string_value):
                                            chinese_values.add(string_value)
                                    except Exception:
                                        pass

                        # 将所有提取到的中文值添加到结果中
                        for value in chinese_values:
                            self.add_chinese_text(value, auth_file_path)
                    except Exception as e:
                        print(f"处理MMKV文件 {auth_file_path} 时出错: {e}")

        elif file_path.endswith('.dat') or file_path.endswith('.config') or file_path.endswith('.json'):
            # 尝试作为JSON文件处理
            json_success = False
            for encoding in ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1']:
                try:
                    with open(file_path, 'r', encoding=encoding) as file:
                        data = json.load(file)
                    for value in recursive_iter(data):
                        if isinstance(value, str) and contains_chinese_characters(value):
                            self.chinese_texts.add(value)
                            # 记录来源
                            if value not in self.text_sources:
                                self.text_sources[value] = []
                            if relative_path not in self.text_sources[value]:
                                self.text_sources[value].append(relative_path)
                    json_success = True
                    break  # 如果成功打开文件，跳出循环
                except UnicodeDecodeError:
                    continue  # 如果编码错误，尝试下一种编码
                except json.JSONDecodeError:
                    break  # 如果不是JSON格式，跳出循环
                except Exception as e:
                    if encoding == 'latin1':  # 只在最后一次尝试时打印错误
                        print(f"处理JSON文件 {file_path} 时出错: {e}")
                    break

            # 如果不是JSON文件，尝试作为二进制文件处理
            if not json_success:
                try:
                    # 尝试以二进制方式读取文件，然后转换为文本
                    with open(file_path, 'rb') as file:
                        binary_data = file.read()

                    # 尝试不同的编码解码二进制数据
                    for encoding in ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1']:
                        try:
                            text = binary_data.decode(encoding, errors='ignore')
                            # 查找所有中文字符
                            chinese_pattern = re.compile('[\u4e00-\u9fff]+')
                            for match in chinese_pattern.finditer(text):
                                chinese_text = match.group()
                                if len(chinese_text) > 1:  # 忽略单个中文字符
                                    self.chinese_texts.add(chinese_text)
                                    # 记录来源
                                    if chinese_text not in self.text_sources:
                                        self.text_sources[chinese_text] = []
                                    if relative_path not in self.text_sources[chinese_text]:
                                        self.text_sources[chinese_text].append(relative_path)
                            break
                        except Exception:
                            continue
                except Exception as e:
                    print(f"处理二进制文件 {file_path} 时出错: {e}")


def main():
    root = tk.Tk()
    ChineseExtractorApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()
